﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.Web;
using System.Data;

namespace OSGeo.MapGuide.UI
{
    public class MgFeatureSourceView : DataSourceView
    {
        private MgFeatureSource _owner;

        public MgFeatureSourceView(MgFeatureSource owner, string name)
            : base(owner, name)
        {
            _owner = owner;
        }

        internal void InitMapGuideSession()
        {
            var source = _owner.MapGuideSessionSource;
            var context = HttpContext.Current;
            string sessionName = _owner.MapGuideSessionName;
            switch (source)
            {
                case SessionParameterSource.Get:
                    _owner.SessionID = context.Request.QueryString[sessionName];
                    break;
                case SessionParameterSource.Post:
                    _owner.SessionID = context.Request.Form[sessionName];
                    break;
                case SessionParameterSource.Session:
                    _owner.SessionID = (context.Session[sessionName] != null) ? context.Session[sessionName].ToString() : string.Empty;
                    break;
            }
        }

        protected override System.Collections.IEnumerable ExecuteSelect(DataSourceSelectArguments arguments)
        {
            if (string.IsNullOrEmpty(_owner.SessionID))
                throw new InvalidOperationException(string.Format(Properties.Resources.ERR_FSVIEW_EMPTY_SESSION, Environment.NewLine));

            if (this.CanSort)
                arguments.AddSupportedCapabilities(DataSourceCapabilities.Sort);
            if (this.CanPage)
                arguments.AddSupportedCapabilities(DataSourceCapabilities.Page);
            if (this.CanRetrieveTotalRowCount)
                arguments.AddSupportedCapabilities(DataSourceCapabilities.RetrieveTotalRowCount);

            arguments.RaiseUnsupportedCapabilitiesError(this);

            MgUserInformation user = new MgUserInformation(_owner.SessionID);
            MgSiteConnection conn = new MgSiteConnection();
            conn.Open(user);

            MgResourceIdentifier fsId = new MgResourceIdentifier(_owner.FeatureSourceID);
            MgFeatureService service = (MgFeatureService)conn.CreateService(MgServiceType.FeatureService);
            MgFeatureQueryOptions query = new MgFeatureQueryOptions();

            string nullString = _owner.NullValueString;
            string className = _owner.SchemaName + ":" + _owner.ClassName;
            //TODO: Support custom property list
            string filter = _owner.SelectFilter;
            if (!string.IsNullOrEmpty(filter))
                query.SetFilter(filter);

            MgFeatureReader reader = service.SelectFeatures(fsId, className, query);
            MgClassDefinition classDef = reader.GetClassDefinition();

            //TODO: I can't use typed objects, so if there is a more efficent way than DataTable, 
            //I'd like to know about it.

            DataTable table = new DataTable(classDef.Name);
            List<DataColumn> pks = new List<DataColumn>();
            Dictionary<string, Func<object>> readerFuncs = new Dictionary<string, Func<object>>();
            foreach (MgPropertyDefinition propDef in classDef.GetProperties())
            {
                string name = propDef.Name;
                if (propDef.PropertyType == MgFeaturePropertyType.DataProperty)
                {
                    MgDataPropertyDefinition dp = (MgDataPropertyDefinition)propDef;
                    switch (dp.DataType)
                    {
                        case MgPropertyType.Blob:
                            readerFuncs[name] = () => { return reader.IsNull(name) ? (object)DBNull.Value : reader.GetBLOB(name).AsByteArray(); };
                            break;
                        case MgPropertyType.Boolean:
                            readerFuncs[name] = () => { return reader.IsNull(name) ? (object)DBNull.Value : reader.GetBoolean(name); };
                            break;
                        case MgPropertyType.Byte:
                            readerFuncs[name] = () => { return reader.IsNull(name) ? (object)DBNull.Value : reader.GetByte(name); };
                            break;
                        case MgPropertyType.Clob:
                            readerFuncs[name] = () => { return reader.IsNull(name) ? (object)DBNull.Value : reader.GetCLOB(name).AsByteArray(); };
                            break;
                        case MgPropertyType.DateTime:
                            readerFuncs[name] = () => { return reader.IsNull(name) ? (object)DBNull.Value : reader.GetDateTime(name).AsDateTime(); };
                            break;
                        case MgPropertyType.Double:
                            readerFuncs[name] = () => { return reader.IsNull(name) ? (object)DBNull.Value : reader.GetDouble(name); };
                            break;
                        case MgPropertyType.Int16:
                            readerFuncs[name] = () => { return reader.IsNull(name) ? (object)DBNull.Value : reader.GetInt16(name); };
                            break;
                        case MgPropertyType.Int32:
                            readerFuncs[name] = () => { return reader.IsNull(name) ? (object)DBNull.Value : reader.GetInt32(name); };
                            break;
                        case MgPropertyType.Int64:
                            readerFuncs[name] = () => { return reader.IsNull(name) ? (object)DBNull.Value : reader.GetInt64(name); };
                            break;
                        case MgPropertyType.Single:
                            readerFuncs[name] = () => { return reader.IsNull(name) ? (object)DBNull.Value : reader.GetSingle(name); };
                            break;
                        case MgPropertyType.String:
                            readerFuncs[name] = () => { return reader.IsNull(name) ? (object)DBNull.Value : reader.GetString(name); };
                            break;
                    }
                    table.Columns.Add(name);
                    if (classDef.GetIdentityProperties().Contains(dp))
                    {
                        pks.Add(table.Columns[name]);
                    }
                }
                else if (propDef.PropertyType == MgFeaturePropertyType.GeometricProperty)
                {
                    readerFuncs[name] = () => { return reader.IsNull(name) ? (object)DBNull.Value : reader.GetGeometry(name).AsGeometry().AsWkt(); };
                    table.Columns.Add(name);
                }
            }
            table.PrimaryKey = pks.ToArray();

            int maximum = 50;//arguments.MaximumRows; //TEST VALUE
            int startFrom = arguments.StartRowIndex;
            int position = 0;

            while (reader.ReadNext())
            {
                if (position >= startFrom)
                {
                    DataRow row = table.NewRow();
                    foreach (string name in readerFuncs.Keys)
                    {
                        try
                        {
                            row[name] = readerFuncs[name]();
                        }
                        catch (Exception)
                        {
                            //row[name] = "FAILED";
                        }
                    }
                    table.Rows.Add(row);
                }
                position++;
                if (table.Rows.Count == maximum)
                    break;
            }
            reader.Close();

            return table.DefaultView;
        }

        protected override int ExecuteDelete(System.Collections.IDictionary keys, System.Collections.IDictionary oldValues)
        {
            if (!CanDelete)
                throw new InvalidOperationException("Delete is not enabled. To enable deletes, set the control property DeleteEnabled = true");

            if (keys.Count == 0)
                throw new InvalidOperationException("No keys specified. If deleting from a GridView, make sure that the DataKeyNames property has been set");

            string filter = GenerateFilter(keys);

            MgUserInformation user = new MgUserInformation(_owner.SessionID);
            MgSiteConnection conn = new MgSiteConnection();
            conn.Open(user);

            MgResourceIdentifier fsId = new MgResourceIdentifier(_owner.FeatureSourceID);
            MgFeatureService service = (MgFeatureService)conn.CreateService(MgServiceType.FeatureService);

            MgDeleteFeatures delete = new MgDeleteFeatures(_owner.ClassName, filter);
            MgFeatureCommandCollection cmds = new MgFeatureCommandCollection();
            cmds.Add(delete);
            MgPropertyCollection result = service.UpdateFeatures(fsId, cmds, _owner.UseTransactions);

            if (result[0].PropertyType == MgPropertyType.String) //Error
                throw new InvalidOperationException(((MgStringProperty)result[0]).GetValue());
            else //Contains affected results
                return ((MgInt32Property)result[0]).GetValue();
        }

        private static string GenerateFilter(System.Collections.IDictionary keys)
        {
            List<string> filters = new List<string>();
            foreach (object k in keys.Keys)
            {
                filters.Add(string.Format("{0} = '{1}'", k, keys[k]));
            }
            string filter = string.Join(" AND ", filters.ToArray());
            return filter;
        }

        private static MgPropertyCollection GetPropertyValues(System.Collections.IDictionary values)
        {
            MgPropertyCollection props = new MgPropertyCollection();
            foreach (object key in values.Keys)
            {
                var value = values[key];
                if (typeof(string) == (value.GetType()))
                    props.Add(new MgStringProperty(key.ToString(), (string)value));
                else if (typeof(int) == (value.GetType()))
                    props.Add(new MgInt32Property(key.ToString(), (int)value));
                else if (typeof(bool) == (value.GetType()))
                    props.Add(new MgBooleanProperty(key.ToString(), (bool)value));
                else if (typeof(DateTime) == (value.GetType()))
                    props.Add(new MgDateTimeProperty(key.ToString(), ((DateTime)value).AsMgDateTime()));
                else if (typeof(short) == (value.GetType()))
                    props.Add(new MgInt16Property(key.ToString(), (short)value));
                else if (typeof(long) == (value.GetType()))
                    props.Add(new MgInt64Property(key.ToString(), (long)value));
                else if (typeof(double) == (value.GetType()))
                    props.Add(new MgDoubleProperty(key.ToString(), (double)value));
                else if (typeof(float) == (value.GetType()))
                    props.Add(new MgSingleProperty(key.ToString(), (float)value));
                else if (typeof(byte) == (value.GetType()))
                    props.Add(new MgByteProperty(key.ToString(), (byte)value));
                else if (typeof(byte[]) == (value.GetType()))
                    props.Add(new MgBlobProperty(key.ToString(), ((byte[])value).AsByteReader()));
                else
                    props.Add(new MgStringProperty(key.ToString(), value.ToString()));
            }
            return props;
        }

        protected override int ExecuteUpdate(System.Collections.IDictionary keys, System.Collections.IDictionary values, System.Collections.IDictionary oldValues)
        {
            if (!CanUpdate)
                throw new InvalidOperationException("Update is not enabled. To enable updates, set the control property UpdateEnabled = true");

            if (keys.Count == 0)
                throw new InvalidOperationException("No keys specified. If updating from a GridView, make sure that the DataKeyNames property has been set");

            string filter = GenerateFilter(keys);
            List<string> keyNames = new List<string>();

            MgUserInformation user = new MgUserInformation(_owner.SessionID);
            MgSiteConnection conn = new MgSiteConnection();
            conn.Open(user);

            MgResourceIdentifier fsId = new MgResourceIdentifier(_owner.FeatureSourceID);
            MgFeatureService service = (MgFeatureService)conn.CreateService(MgServiceType.FeatureService);

            MgPropertyCollection updateValues = GetPropertyValues(values);
            MgUpdateFeatures update = new MgUpdateFeatures(_owner.ClassName, updateValues, filter);

            MgFeatureCommandCollection cmds = new MgFeatureCommandCollection();
            cmds.Add(update);
            MgPropertyCollection result = service.UpdateFeatures(fsId, cmds, _owner.UseTransactions);
            return ((MgInt32Property)result[0]).GetValue();
        }

        protected override int ExecuteInsert(System.Collections.IDictionary values)
        {
            if (!CanInsert)
                throw new InvalidOperationException("Insert is not enabled. To enable inserts, set the control property InsertEnabled = true");

            throw new NotImplementedException();
        }

        public override bool CanPage { get { return true; } }

        public override bool CanInsert { get { return true; } }

        public override bool CanSort { get { return true; } }

        public override bool CanDelete { get { return _owner.DeleteEnabled; } }

        public override bool CanUpdate { get { return _owner.UpdateEnabled; } }

        public override bool CanRetrieveTotalRowCount { get { return false; } }

        internal void RaiseChangedEvent()
        {
            OnDataSourceViewChanged(EventArgs.Empty);
        }
    }
}
